package ysa;

import static java.lang.Math.signum;
import static java.lang.Math.toRadians;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.Random;

import robocode.AdvancedRobot;
import robocode.DeathEvent;
import robocode.ScannedRobotEvent;
import robocode.util.Utils;

/**
 * The robot which always see one of it's enemies.
 */
public class YsaRobot extends AdvancedRobot {

	private static final double RADAR_DELTA_RAD = toRadians(5);
	private static final double FIRE_DISTANCE_PARAM = 100;
	private static final double STRONG_FIRE = 5;
	private static final double WEAK_FIRE = 2;
	private static final int DISTANCE_COEF = -400;
	private static final int DISTANCE_FREE_TERM = 200;

	private boolean isAlive = true;

	private double enemyX = -1;
	private double enemyY = -1;
	private double enemyDistance = -1;

	private Random random = new Random();

	/**
	 * The main cycle of our robot. It's behavior shold be described here.
	 */
	@Override
	public void run() {
		setBodyColor(Color.BLACK);
		setRadarColor(Color.WHITE);
		setGunColor(Color.BLACK);

		// attempts to find an enemy turning the radar
		setTurnRadarRightRadians(Double.POSITIVE_INFINITY);

		while (isAlive) {
			if (enemyX > -1) { // if an enemy has been found
				final double radarTurn = getRadarTurn();
				setTurnRadarRightRadians(radarTurn); // 1
				final double bodyTurn = getBodyTurn();
				setTurnRightRadians(bodyTurn); // 2

				if (getDistanceRemaining() == 0) {
					final double distance = getDistance();
					setAhead(distance); // 3
				}
				final double gunTurn = getGunTurn();
				setTurnGunRightRadians(gunTurn); // 4

				System.out.println("Distance: " + enemyDistance);
				if (enemyDistance <= FIRE_DISTANCE_PARAM) {
					setFire(STRONG_FIRE); // 5
				} else {
					setFire(WEAK_FIRE);
				}
			}

			execute();
		}
	}

	/**
	 * The event handler which works out when the enemy is in the radar scope.
	 * After the enemy has been found, we should calculate it's coordinates.
	 */
	@Override
	public void onScannedRobot(ScannedRobotEvent event) {
		// the absolute angle to an enemy
		final double alphaToEnemy = getHeadingRadians()
				+ event.getBearingRadians();

		enemyDistance = event.getDistance();
		// use the sine and cosine definitions from a school
		enemyX = getX() + Math.sin(alphaToEnemy) * enemyDistance;
		enemyY = getY() + Math.cos(alphaToEnemy) * enemyDistance;
	}

	/**
	 * The event handler which works out when our robot has been killed.
	 */
	@Override
	public void onDeath(DeathEvent event) {
		isAlive = false;
	}

	/**
	 * Draws the calculated position of an enemy on the battle field.
	 */
	@Override
	public void onPaint(Graphics2D g) {
		if (enemyX > -1) {
			g.setColor(Color.WHITE);
			g.drawRect((int) (enemyX - getWidth() / 2),
					(int) (enemyY - getHeight() / 2), (int) getWidth(),
					(int) getHeight());
		}
	}

	private double getRadarTurn() {
		// our robot should see the enemy permanently
		final double alphaToEnemy = angleTo(getX(), getY(), enemyX, enemyY);

		final double sign = (alphaToEnemy != getRadarHeadingRadians()) ? signum(Utils
				.normalRelativeAngle(alphaToEnemy - getRadarHeadingRadians()))
				: 1;

		return Utils.normalRelativeAngle(alphaToEnemy
				- getRadarHeadingRadians() + RADAR_DELTA_RAD * sign);
	}

	private static double angleTo(double baseX, double baseY, double x, double y) {
		double theta = Math.asin((y - baseY)
				/ Point2D.distance(x, y, baseX, baseY))
				- Math.PI / 2;
		if (x >= baseX && theta < 0) {
			theta = -theta;
		}
		return (theta %= Math.PI * 2) >= 0 ? theta : (theta + Math.PI * 2);
	}

	private double getDistance() {
		return DISTANCE_FREE_TERM + DISTANCE_COEF * random.nextInt(3);
	}

	private double getBodyTurn() {
		final double alphaToMe = angleTo(enemyX, enemyY, getX(), getY());

		final double lateralDirection = signum((getVelocity() != 0 ? getVelocity()
				: 1)
				* Math.sin(Utils.normalRelativeAngle(getHeadingRadians()
						- alphaToMe)));

		final double desiredHeading = Utils.normalAbsoluteAngle(alphaToMe
				+ Math.PI / 2 * lateralDirection);

		final double normalHeading = getVelocity() >= 0 ? getHeadingRadians()
				: Utils.normalAbsoluteAngle(getHeadingRadians() + Math.PI);

		return Utils.normalRelativeAngle(desiredHeading - normalHeading);
	}
	
	private double getGunTurn() {
		return Utils
				.normalRelativeAngle(angleTo(getX(), getY(), enemyX, enemyY)
						- getGunHeadingRadians());
	}
}